/* 
 Copyright 2007 Simon Lebettre
 This file is part of BeanCooker.

 BeanCooker is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.

 BeanCooker is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>
 */ 
package net.sf.beancooker.dataproviders;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import net.sf.beancooker.BeanCooker;
import net.sf.beancooker.IDataProvider;



/**
 * BaseDataProvider.
 */
@SuppressWarnings("unchecked")
public abstract class BaseDataProvider implements IDataProvider {


	protected String decimalValueString;

	
	protected String integerValueString;

	
	protected String byteValueString;

	
	protected String stringValue;

	
	protected char charValue;

	
	protected Calendar calendar;

	
	protected BeanCooker callback;

	
	protected Boolean booleanValue;

	
	protected int arraysSize = 3;

	
	protected boolean isAppendingFieldName = true;

	private boolean isInitNeeded = true;
	
	/**
	 * optional init method to implement in subclasses
	 * @throws Exception custom init methods can throw any exception
	 */
	protected void init() throws Exception{
		//optional init method to implement in subclasses.
	}
	
	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getValue(java.lang.Class, java.lang.String, int)
	 */
	public final Object getValue(Class clazz, String fieldName, int index) {
		
		//optional init phase, this is needed to have some init without depending on parametrized constructors 
		if (this.isInitNeeded()) {
			try {
				init();
			} catch (Exception e) {
				throw new RuntimeException("unable to initialize", e);
			}
			this.setInitNeeded(false);
		}
		return getValueAfterInit(clazz, fieldName, index);
	}	
	
	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getValueAfterInit(java.lang.Class, java.lang.String, int)
	 */
	public Object getValueAfterInit(Class clazz, String fieldName, int index) {
		Object value = null;
		if (isRejected(clazz)) {
			value = null;
		} else if (clazz.isEnum()) {
			value = getEnumValue(clazz);
		} else {
			if (clazz.equals(String.class)) return getStringValue(fieldName);				
			if (clazz.equals(Calendar.class)) return getCalendar();
			if (clazz.equals(XMLGregorianCalendar.class)) return getXmlCalendar();
			if (clazz.equals(Date.class)) return getDate();
			if (clazz.equals(Boolean.TYPE)) return getBooleanValue();
			if (clazz.equals(Boolean.class)) return getBooleanValue();
			if (clazz.equals(Character.TYPE)) return getCharValue();
			if (clazz.equals(Character.class)) return getCharValue();
			if (clazz.equals(Byte.TYPE)) return Byte.valueOf(getByteValueString());
			if (clazz.equals(Byte.class)) return Byte.valueOf(getByteValueString());
			if (clazz.equals(Short.TYPE)) return Short.valueOf(getIntegerValueString());
			if (clazz.equals(Short.class)) return Short.valueOf(getIntegerValueString());
			if (clazz.equals(Integer.TYPE)) return Integer.valueOf(getIntegerValueString());
			if (clazz.equals(Integer.class)) return Integer.valueOf(getIntegerValueString());
			if (clazz.equals(Long.TYPE)) return Long.valueOf(getIntegerValueString());
			if (clazz.equals(Long.class)) return Long.valueOf(getIntegerValueString());
			if (clazz.equals(BigInteger.class)) return new BigInteger(getIntegerValueString());
			if (clazz.equals(BigDecimal.class)) return new BigDecimal(getDecimalValueString());
			if (clazz.equals(Float.TYPE)) return Float.valueOf(getDecimalValueString());
			if (clazz.equals(Float.class)) return Float.valueOf(getDecimalValueString());
			if (clazz.equals(Double.TYPE)) return Double.valueOf(getIntegerValueString());
			if (clazz.equals(Double.class)) return Double.valueOf(getIntegerValueString());
		}

		return value;
	}


	/**
	 * Gets a value for strings
	 * 
	 * @param fieldName the field name
	 * 
	 * @return the string value
	 */
	private String getStringValue(String fieldName) {
		String s = getStringValue();
		if (isAppendingFieldName()) s = fieldName + "_" + s;
		return s;
	}

	/**
	 * Gets the enum value.
	 * 
	 * @param clazz the Enum class to mock
	 * 
	 * @return the enum value (takes value in the middle of the enum to try avoiding default value)
	 */
	public Object getEnumValue(Class clazz) {
		Object[] enumConstants = clazz.getEnumConstants();
		return enumConstants[enumConstants.length / 2];// take constant in the middle to try avoiding default value
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getDate()
	 */
	public Date getDate() {
		return this.getCalendar().getTime();
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getXmlCalendar()
	 */
	public XMLGregorianCalendar getXmlCalendar() {
		try {
			XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar((GregorianCalendar) this.getCalendar());
			return xmlGregorianCalendar;
		} catch (DatatypeConfigurationException e) {
			// won't happen with this implementation, this.calendar is fixed
			e.printStackTrace();
			return null;
		}

	}

	/**
	 * Checks if is rejected.
	 * 
	 * @param clazz the clazz
	 * 
	 * @return true, if is rejected
	 */
	public boolean isRejected(Class clazz) {
		return callback.isRejected(clazz);
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getDecimalValueString()
	 */
	public String getDecimalValueString() {
		return this.decimalValueString;
	}

	/**
	 * Sets the decimal value string.
	 * 
	 * @param aDecimalValueString the new decimal value string
	 */
	public void setDecimalValueString(String aDecimalValueString) {
		this.decimalValueString = aDecimalValueString;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getIntegerValueString()
	 */
	public String getIntegerValueString() {
		return this.integerValueString;
	}

	/**
	 * Sets the integer value string.
	 * 
	 * @param aIntegerValueString the new integer value string
	 */
	public void setIntegerValueString(String aIntegerValueString) {
		this.integerValueString = aIntegerValueString;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getStringValue()
	 */
	public String getStringValue() {
		return this.stringValue;
	}

	/**
	 * Sets the string value.
	 * 
	 * @param aStringValue the new string value
	 */
	public void setStringValue(String aStringValue) {
		this.stringValue = aStringValue;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getByteValueString()
	 */
	public String getByteValueString() {
		return this.byteValueString;
	}

	/**
	 * Sets the byte value string.
	 * 
	 * @param aByteValueString the new byte value string
	 */
	public void setByteValueString(String aByteValueString) {
		this.byteValueString = aByteValueString;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getCallback()
	 */
	public BeanCooker getCallback() {
		return this.callback;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#setCallback(net.sf.beancooker.BeanCooker)
	 */
	public void setCallback(BeanCooker aCallback) {
		this.callback = aCallback;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see net.sf.beancooker.IDataProvider#getArraysSize()
	 */
	public int getArraysSize() {
		return this.arraysSize;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see net.sf.beancooker.IDataProvider#setArraysSize(int)
	 */
	public void setArraysSize(int size) {
		this.arraysSize = size;

	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getCharValue()
	 */
	public char getCharValue() {
		return this.charValue;
	}

	/**
	 * Sets the char value.
	 * 
	 * @param aCharValue the new char value
	 */
	public void setCharValue(char aCharValue) {
		this.charValue = aCharValue;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getBooleanValue()
	 */
	public Boolean getBooleanValue() {
		return this.booleanValue;
	}

	/**
	 * Sets the boolean value.
	 * 
	 * @param aBooleanValue the new boolean value
	 */
	public void setBooleanValue(Boolean aBooleanValue) {
		this.booleanValue = aBooleanValue;
	}

	/* (non-Javadoc)
	 * @see net.sf.beancooker.IDataProvider#getCalendar()
	 */
	public Calendar getCalendar() {
		return this.calendar;
	}

	/**
	 * Sets the calendar.
	 * 
	 * @param aCalendar the new calendar
	 */
	public void setCalendar(Calendar aCalendar) {
		this.calendar = aCalendar;
	}

	/**
	 * convenience setter for calendar from string tokens
	 * Remember Month is 0 indexed (Calendar.JANUARY = 0 )
	 * 
	 * @param tokens like "2008,07,28,00,00,00" or "2008,07,28"
	 */
	public void setCalendar(String tokens) {
		
		Calendar c = Calendar.getInstance();
		
		String[] params = tokens.split(",");
		int year = Integer.parseInt(params[0]);
		int month = Integer.parseInt(params[1]);
		int date = Integer.parseInt(params[2]);
		
		if (params.length > 3){
			int hourOfDay = Integer.parseInt(params[3]);
			int minute = Integer.parseInt(params[4]);
			int second = Integer.parseInt(params[5]);
			c.set(year, month, date, hourOfDay, minute, second);
		} else {
			c.set(year, month, date);
		}
				
		this.calendar = c;
	}

	/**
	 * Checks if is appending field name.
	 * 
	 * @return true, if is appending field name
	 */
	public boolean isAppendingFieldName() {
		return this.isAppendingFieldName;
	}

	/**
	 * Sets the appending field name.
	 * 
	 * @param aIsAppendingFieldName the new appending field name
	 */
	public void setAppendingFieldName(boolean aIsAppendingFieldName) {
		this.isAppendingFieldName = aIsAppendingFieldName;
	}

	public boolean isInitNeeded() {
		return isInitNeeded;
	}

	public void setInitNeeded(boolean isInitNeeded) {
		this.isInitNeeded = isInitNeeded;
	}

}
